package de.tempel.pong;

import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import processing.core.PApplet;
import SimpleOpenNI.SimpleOpenNI;
import de.tempel.pong.ai.PongAI;
import de.tempel.pong.ai.SimplePongAI;
import de.tempel.pong.collison.AbstractCollisionManager;
import de.tempel.pong.collison.ContinuousCollisionManager;
import de.tempel.pong.collison.DiscreteCollisionManager;
import de.tempel.pong.collison.SubsequentDiscreteCollisionManager;
import de.tempel.pong.objects.Ball;
import de.tempel.pong.objects.Racket;
import de.tempel.pong.objects.ScoreBoard;
import de.tempel.pong.objects.basic.Vector;
import de.tempel.pong.objects.interfaces.Drawable;
import de.tempel.pong.objects.interfaces.Moveable;

/**
 * Spielklasse mit Main-Methode
 * 
 * @author mathias
 * 
 */
public class PingPongPeng extends PApplet {

	private static final long serialVersionUID = 1L;

	/**
	 * Größen
	 */
	private static final int FIELD_WIDHT = 800;
	private static final int FIELD_HEIGHT = 600;

	private static final int BASE_SIZE = 20;
	private static final int RACKET_WIDHT = 20;
	private static final int RACKET_HEIGHT = 180;

	private static final int DIGIT_DISTANCE = 10;
	private static final int DIGIT_WIDTH = 30;
	private static final int DIGIT_HEIGHT = DIGIT_WIDTH + 2 * DIGIT_WIDTH / 3;

	/**
	 * AI
	 */
	private PongAI pongAIPlayer1 = null;
	private PongAI pongAIPlayer2 = null;

	/**
	 * Spielobjekte
	 */
	private final List<Drawable> drawable = new ArrayList<Drawable>();
	private final List<Moveable> moveable = new ArrayList<Moveable>();

	private Racket playerOne;
	private Racket playerTwo;
	private List<Racket> rackets;
	private Ball ball;

	private ScoreBoard scoreBoardPlayerOne;
	private ScoreBoard scoreBoardPlayerTwo;

	/**
	 * Collision
	 */
	private AbstractCollisionManager cm;

	/**
	 * Bewegung
	 */
	private Point2D startBallPosition;

	// Timestamp seitdem Informationionen auf dem Spielfeld angezeigt werden
	private long showGameInformation;

	public SimpleOpenNI context;

	/**
	 * Standardkonstruktor
	 */
	public PingPongPeng() {
		setupCam();
		showManuel();
		setupObjects();

	}

	private void setupCam() {

	}

	/**
	 * Zeigt Gebrauchsanweisung an
	 */
	private void showManuel() {
		URL infoIconUrl = PingPongPeng.class.getClassLoader().getResource(
				"pong.jpg");
		ImageIcon icon = new ImageIcon(infoIconUrl);

		JOptionPane.showMessageDialog(
				this,
				"Wilkommen bei PingPongPeng! \n \n"
						+ //
						"Eine kurze Gebrauchsanweisung \n"
						+ //
						"Spieler 1 (links) : \n"
						+ //
						"\t - Hoch:       'w' \n"
						+ //
						"\t - Runter:    's' \n"
						+ //
						"\t - AI an/aus:'y' \n"
						+ //
						"Spieler 2 (rechts)  \n"
						+ //
						"\t - Hoch:       'Pfeil Hoch' \n"
						+ //
						"\t - Runter:    'Pfeil Runter' \n"
						+ //
						"\t - AI an/aus:'x' \n"
						+ //
						"Allgemein \n"
						+ //
						"\t - Reset:  'r' \n"
						+ //
						"\t - Geschwindigkeit: \n"
						+ //
						"\t   Hoch:  '+' \n"
						+ //
						"\t   Runter '-' \n"
						+ //
						"\t - Wechseln der CollisionStrategy: \n"
						+ //
						"\t "
						+ DiscreteCollisionManager.class.getSimpleName()
						+ ": '1' \n"
						+ //
						"\t "
						+ SubsequentDiscreteCollisionManager.class
								.getSimpleName()
						+ ": '2' \n"
						+ //
						"\t "
						+ ContinuousCollisionManager.class.getSimpleName()
						+ ": '3'", "Wilkommen bei PingPongPeng",
				JOptionPane.OK_OPTION, icon);
	}

	/**
	 * Setzt Spielobjekte zusammen
	 */
	private void setupObjects() {

		Point positionPlayerOne = new Point(BASE_SIZE, BASE_SIZE);
		Point positionPlayerTwo = new Point(FIELD_WIDHT - RACKET_WIDHT
				- BASE_SIZE, FIELD_HEIGHT - RACKET_HEIGHT - BASE_SIZE);

		playerOne = new Racket(positionPlayerOne, RACKET_WIDHT, RACKET_HEIGHT,
				FIELD_WIDHT, FIELD_HEIGHT);
		playerTwo = new Racket(positionPlayerTwo, RACKET_WIDHT, RACKET_HEIGHT,
				FIELD_WIDHT, FIELD_HEIGHT);

		scoreBoardPlayerOne = new ScoreBoard(FIELD_WIDHT / 2 - BASE_SIZE - 3
				* DIGIT_WIDTH - 2 * DIGIT_DISTANCE, BASE_SIZE, DIGIT_HEIGHT,
				DIGIT_WIDTH, DIGIT_DISTANCE);
		scoreBoardPlayerTwo = new ScoreBoard(FIELD_WIDHT / 2 + BASE_SIZE,
				BASE_SIZE, DIGIT_HEIGHT, DIGIT_WIDTH, DIGIT_DISTANCE);

		startBallPosition = new Point2D.Double(FIELD_WIDHT / 2,
				FIELD_HEIGHT / 2);
		Vector movementBall = new Vector(150, 100);
		Point2D ballPosition = new Point2D.Double(FIELD_WIDHT / 2,
				FIELD_HEIGHT / 2);
		ball = new Ball(ballPosition, BASE_SIZE, movementBall);

		drawable.add(scoreBoardPlayerOne);
		drawable.add(scoreBoardPlayerTwo);
		drawable.add(playerOne);
		drawable.add(playerTwo);
		drawable.add(ball);

		moveable.add(playerOne);
		moveable.add(playerTwo);
		moveable.add(ball);

		rackets = new ArrayList<Racket>();
		rackets.add(playerOne);
		rackets.add(playerTwo);
		cm = new DiscreteCollisionManager(ball, rackets, this);

		showGameInformation = System.currentTimeMillis();

	}

	@Override
	public void setup() {

		// context = new SimpleOpenNI(this);
		context = new SimpleOpenNI(this, SimpleOpenNI.RUN_MODE_MULTI_THREADED);

		// enable depthMap generation
		context.enableDepth();

		// enable skeleton generation for all joints
		context.enableUser(SimpleOpenNI.SKEL_PROFILE_ALL);

		size(FIELD_WIDHT, FIELD_HEIGHT);
	}

	@Override
	public void draw() {

		float dT = 1.0f / frameRate;
		update(dT);

	}

	private void update(float dT) {
		// clear
		fill(0);
		this.rect(0, 0, width, height);
		fill(255);
		this.rect((float) (width / 2 - 2.5), 0, 5, height);

		// Cam
		context.update();
		image(context.depthImage(), 0, 0);

		// move
		for (Moveable m : moveable) {
			m.move(dT);
		}

		// draw
		for (Drawable d : drawable) {
			d.draw(this);
		}

		// collision
		cm.checkCollisions(dT);

		// check points
		Point2D ballBasePoint = ball.getPosition();

		if (ballBasePoint.getX() < 0) {

			ball.setPosition(new Point2D.Double(startBallPosition.getX(),
					startBallPosition.getY()));
			scoreBoardPlayerTwo.addPoint();
			return;
		}

		if (ballBasePoint.getX() + ball.getWidth() > FIELD_WIDHT) {

			ball.setPosition(new Point2D.Double(startBallPosition.getX(),
					startBallPosition.getY()));
			scoreBoardPlayerOne.addPoint();
			return;
		}

		// AI
		if (pongAIPlayer1 != null) {
			pongAIPlayer1.calculateNextPosition();
		}

		if (pongAIPlayer2 != null) {
			pongAIPlayer2.calculateNextPosition();
		}

		drawInfo();
		drawFamerate(frameRate);
	}

	/**
	 * Zeichnet allgemeineSpielinformationen
	 */
	private void drawInfo() {

		if (System.currentTimeMillis() - showGameInformation < 8000) {
			fill(140);
			this.text("GameInformation:", 50, 475);
			this.text("CollisionStrategy: \t\t\t"
					+ cm.getClass().getSimpleName(), 50, 490);

			if (pongAIPlayer1 != null) {
				this.text("AI for Player1 enabled", 50, 505);
			} else {
				this.text("AI for Player1 disabled", 50, 505);
			}

			if (pongAIPlayer2 != null) {
				this.text("AI for Player2 enabled", 50, 520);
			} else {
				this.text("AI for Player2 disabled", 50, 520);
			}

			double speed = ball.getMovement().getX();
			if (speed < 0) {
				speed = speed * -1;
			}
			this.text("Gamespeed: " + speed, 50, 535);

			fill(255);
		}

	}

	/**
	 * Zeichnet allgemeineSpielinformationen
	 * 
	 * @param frameRate
	 */
	private void drawFamerate(float frameRate) {

		fill(140);
		this.text("CurrentFramerate:" + frameRate, 50, 550);

	}

	@Override
	public void keyPressed(KeyEvent ke) {

		if (ke.getKeyCode() == UP) {

			playerTwo.moveUp();

		} else if (ke.getKeyCode() == DOWN) {

			playerTwo.moveDown();

		} else if (ke.getKeyCode() == RIGHT) {

			playerTwo.moveRight();

		} else if (ke.getKeyCode() == LEFT) {

			playerTwo.moveLeft();

		} else if (ke.getKeyChar() == 'w') {

			playerOne.moveUp();

		} else if (ke.getKeyChar() == 's') {

			playerOne.moveDown();

		} else if (ke.getKeyChar() == 'd') {

			playerOne.moveRight();

		} else if (ke.getKeyChar() == 'a') {

			playerOne.moveLeft();

		} else if (ke.getKeyChar() == '1') {

			cm = new DiscreteCollisionManager(ball, rackets, this);
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == '2') {

			cm = new SubsequentDiscreteCollisionManager(ball, rackets, this);
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == '3') {

			cm = new ContinuousCollisionManager(ball, rackets, this);
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == '+') {

			ball.getMovement().multiply(1.5d);
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == '-') {

			ball.getMovement().multiply(0.5d);
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == 'y') {

			if (pongAIPlayer1 == null) {
				pongAIPlayer1 = new SimplePongAI(ball, playerOne);
			} else {
				pongAIPlayer1 = null;
			}
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == 'x') {

			if (pongAIPlayer2 == null) {
				pongAIPlayer2 = new SimplePongAI(ball, playerTwo);
			} else {
				pongAIPlayer2 = null;
			}
			showGameInformation = System.currentTimeMillis();

		} else if (ke.getKeyChar() == '/') {

			float newFloat = frameRate - 10;
			this.frameRate(newFloat);

		} else if (ke.getKeyChar() == '*') {

			float newFloat = frameRate + 10;
			this.frameRate(newFloat);
		} else if (ke.getKeyChar() == 'r') {

			ball.setPosition(startBallPosition);
			scoreBoardPlayerOne.setPoints(0);
			scoreBoardPlayerTwo.setPoints(0);

		}

	}

	@Override
	public void keyReleased(KeyEvent ke) {

		if (ke.getKeyCode() == UP) {

			playerTwo.stopMoving();

		} else if (ke.getKeyCode() == DOWN) {

			playerTwo.stopMoving();

		} else if (ke.getKeyChar() == 'q') {

			playerOne.stopMoving();

		} else if (ke.getKeyChar() == 'a') {

			playerOne.stopMoving();
		}
	}

	public static void main(String[] args) {
		PApplet.main(new String[] { "de.tempel.pong.PingPongPeng" });
	}

}
